<script setup lang="ts">
import {h} from 'vue'
import {SyncOutlined,CopyOutlined,SoundFilled,SoundOutlined,LoadingOutlined } from '@ant-design/icons-vue';
import Avatar from '@/components/Avatar.vue';
import { Bubble,Attachments } from 'ant-design-x-vue';
import markdownit from 'markdown-it';
const { message } = App.useApp();

import type {BubbleMessageType} from "@/types";
import {apiAiAgent, apiAiFetch} from "@/api/ai.ts";
import {App} from "ant-design-vue";
import useSpeech from "@/hooks/useSpeech.ts";
// const hideAvatar: CSSProperties = {
// 	visibility: 'hidden',
// };
/* 用到的工具 */
const currentRoute = useRoute();
const previewUrl = import.meta.env.VITE_API_PREVIEW;
const md = markdownit({ html: true, breaks: true });
const {storeUser,storeCacheSend} = useStore()

/* emits */
const emit = defineEmits(['onNewConversation','onClearHistory']);

/* data */
const refTerminal=ref()
const refSender=ref()
const loading = ref(false)
const inPush = inject('inPush',ref(false));
let inThink = false; //当前是否进入到思维链的推流输出中？
let conversationId = currentRoute.params.conversationId as string||undefined //本次会话id
const currentTitle= inject('currentTitle',ref('新的对话'))
let taskId='' //用来暂存每次发送/接收消息时对应的taskId
const hisList = ref<any>([]) //对话的聊天历史
hisList.value = [
	// {role:'user1',content:'Good morning, how are you?',taskId:'1111'},
	// {role:'assistant',content:'Hi, good morning, I\'m fine!',taskId:'2222', thinkContent:'',thinkStatus:'pending',  }
]
/*播放相关*/
const { audioSts,speechText, playSpeak, pauseSpeak, } = useSpeech({source:'chat'})

/* methods */
const { pushPath,replacePath } = usePath()
// 发送消息事件
const handleSubmit = (sendInfo:any)=>{
	//console.log(sendInfo)
	if(hisList.value.length===0 && ( !conversationId || conversationId==='' ) ) {
		//新对话中首次发送消息，则需要手动制造一个UUID作为新会话ID，然后跳转到一个新的对话框中进行进一步操作
		apiAiFetch.buildConversationId((body:any)=>{
			storeCacheSend.setCache(sendInfo) //将要发送的消息缓存到pinia中，待有传参的路由页打开后再去处理
			pushPath('/chat/ask/'+body.conversation_id)
		})
		return;
	}else{
		//在带有conversationId传参的路由页中，请求才正式开始
		if(sendInfo.uploadList && sendInfo.uploadList.length>0){
			hisList.value.push( { role:'user'+storeUser.sex, content:'', taskId:taskId, files:sendInfo.uploadList } , )
		}
		hisList.value.push( { role:'user'+storeUser.sex, content:sendInfo.msg, taskId:taskId, },)
		hisList.value.push( { role:'assistant', content:'', taskId:taskId, loading:true }, )


		let uploadList:any[] = []
		let type = sendInfo.uploadType===0? 'image' : sendInfo.uploadType===1? 'document':'audio';
		if(sendInfo.uploadList){
			for( const file of sendInfo.uploadList){
				uploadList.push({ upload_file_id:file.uid, transfer_method:'local_file', type:type, })
			}
		}

		apiAiAgent.request( {message:{content:sendInfo.msg, mode: sendInfo.mode, conversationId:conversationId, files:uploadList}}, {
			onUpdate: (data:BubbleMessageType)=>{
				//console.log(data)
				//todo: 还需要补充大模型推流文件附件过来的展示处理
				if(data.event && data.event==='workflow_started') {

					conversationId = data.conversationId || ''
					taskId = data.taskId || ''
					// }else if(data.event && data.event==='node_finished'){
					//
				}else{
					if(data.mode){ hisList.value[hisList.value.length-1].loading = false } //解除loading状态，开始推流
					if(data.content.indexOf('<think>')===0){  //处理<think>标签
						inThink = true
						hisList.value[hisList.value.length-1].thinkContent = data.content.replace('<think>','')
						hisList.value[hisList.value.length-1].thinkStatus = 'pending'
					}else if(data.content.indexOf('</think>')>=0){  //处理<think>标签
						inThink = false
						const tmp = data.content.split('</think>')
						hisList.value[hisList.value.length-1].thinkContent += tmp[0]
						hisList.value[hisList.value.length-1].content += tmp[1];
						hisList.value[hisList.value.length-1].thinkStatus = 'success';
					}else{
						if(inThink){
							hisList.value[hisList.value.length-1].thinkContent += data.content
						}else{
							hisList.value[hisList.value.length-1].content += data.content
						}
					}
					nextTick(()=>{refTerminal.value.scrollToBottom()})
				}
			},
			onSuccess: (data:BubbleMessageType)=>{
				inPush.value = false;
				hisList.value[hisList.value.length-1].content += data.content
				nextTick(()=>{refTerminal.value.scrollToBottom()})
			},
			onError: (error:any)=>{
				if(hisList.value[hisList.value.length-1].thinkStatus && inThink) {
					inThink = false
					hisList.value[hisList.value.length-1].thinkStatus = 'error';
				}
				console.log(error)
			},
		} );
	}
}

const handleStop=()=>{
	apiAiFetch.stopMessage('chat', taskId, ()=>{
		if(hisList.value[hisList.value.length-1].thinkStatus){
			hisList.value[hisList.value.length-1].thinkStatus = 'error';
		}
	})
}

const getAvatar =(role:string)=>{
	if(role==='user1'||role==='user0'){
		return h(Avatar,{role:role})
	}else if(role==='assistant'){
		return h(Avatar,{role:'assistant'})
	}else {
		return undefined
	}
}
//对某个提示的答案觉得不满意后，让AI重新回答
const reAsk = (index:number)=>{
	refSender.value.setTextContent( hisList.value[index-1].content );
	nextTick(()=>{
		refSender.value.handleMsgSubmit(refSender.value.value)
	})
}
//在开场白推荐的提问语列表中选一个，让AI进行回答
const askWithText = (text:string)=>{
	refSender.value.setTextContent( text );
	nextTick(()=>{
		refSender.value.handleMsgSubmit(refSender.value.value)
	})
}
//复制消息内容
const copyContent = (text:string)=>{
	navigator.clipboard.writeText(text)
		.then(() => {
			message.success('文本已复制到剪贴板');
		})
		.catch(err => {
			message.error('复制失败:'+(err.message||''));
		});
}

//附件被点击事件
const handleAttachClick = (file:any)=>{
	//console.log(file)
	if(file.type==='document'){
		refTerminal.value.downloadFile(file.url,file.name)
	}
}

//清除历史记录事件
const handleClearHistory=()=>{
	//先删除本会话
	apiAiFetch.deleteConversation('chat', conversationId as string, ()=>{
		//再生成一个新的空白对话
		apiAiFetch.buildConversationId((body:any)=>{
			//再将原正数的对话标题给到新空白对话
			apiAiFetch.renameConversation(body.conversation_id, currentTitle.value,false, ()=>{
				//触发方法：修改左侧菜单栏中的对话ID，并跳转到新对话
				emit('onClearHistory', conversationId, body.conversation_id, 'ask')
			})
		})
	})
}

//beforeMounted : // 当访问本页面时如果路由传值了会话ID，则为新页面提交首次提问、或者读取历史会话
if(conversationId && conversationId!==''){
	//该情况是新建会话、且首发第一条信息的的情况
	if(storeCacheSend.msg){
		//如果为新会话且为初始状态，则对该会话进行重全命名
		const newName = storeCacheSend.msg.slice(0, 30);
		apiAiFetch.renameConversation(conversationId as string, newName,false, ()=>{
			//触发方法，让左侧新生成一个会话菜单链接
			//{ key:'0-1', label:'通用咨询1通用咨询1通用咨询1通用咨询1', timestamp:new Date().getTime(),group:'今天', icon:h(MessageOutlined)},
			emit('onNewConversation',conversationId,newName,'ask')
		})
		handleSubmit({
			msg : storeCacheSend.msg,
			mode : storeCacheSend.mode,
			linkNet : storeCacheSend.linkNet,
		})
		let mode =storeCacheSend.mode, linkNet = storeCacheSend.linkNet;
		nextTick(()=>{
			//console.log(mode,linkNet)
			refSender.value.llmMode = mode //确定会话中最后一条消息用的大模型
			refSender.value.linkNet = linkNet
		})
		storeCacheSend.clean()
	}
	//该情况是需要读取这对话的历史聊天记录的情况
	else {
		inPush.value=true
		loading.value=true
		let loadTimer:any = setInterval(()=> {
			if(storeUser.id && storeUser.id!=='') {
				clearInterval(loadTimer);
				loadTimer = null;
				apiAiFetch.getMessageHistory( conversationId as string,
					(body: any) => {
						loading.value=false
						let defOpt = false
						for (let i=body.data.length-1; i>-1;i--) {
							//todo: 对retriever_resources引用和归属分段列表做展示整理
							const item = body.data[i]
							if(item.answer===' '){continue;}
							if(!defOpt){
								refSender.value.llmMode = item.inputs?.mode //确定会话中最后一条消息用的大模型
								refSender.value.linkNet = item.inputs?.linkNet
								defOpt = true
							}

							//插入AI的回答
							let content = item.answer.split('</think>');
							//处理AI可能返回的文件附件
							let resFiles:any[] = []
							for(let file of item.message_files){
								if(file.belongs_to!=='assistant')	continue;
								resFiles.push({ uid:file.id, name:file.filename, size:file.size,url:previewUrl+file.url,type:file.type})
							}
							if(content.length===1){ //没有think的情况
								let tmp = { role:'assistant',content:content[0],taskId:taskId,
									thinkContent:<string|undefined>undefined,thinkStatus:<string|undefined>undefined,files:<any[]|undefined>undefined };
								if(tmp.content===''){
									tmp.thinkContent='思考中断...'
									tmp.thinkStatus='error'
								}
								if(resFiles.length>0)	tmp.files = resFiles
								hisList.value.unshift(tmp)
							}else if(content.length===2){
								hisList.value.unshift({role:'assistant',content:content[1],taskId:taskId, thinkContent:content[0].slice(7), thinkStatus:'success', files:resFiles.length>0? resFiles:undefined })
							}

							//插入用户的提问
							hisList.value.unshift( { role:'user'+storeUser.sex, content:item.query, taskId:taskId, }, )
							//插入用户上传附件的展示
							let files:any[] = []
							for(let file of item.message_files){
								if(file.belongs_to!=='user')	continue;
								files.push({ uid:file.id, name:file.filename, size:file.size,url:previewUrl+file.url,type:file.type})
							}
							if(files.length>0)	hisList.value.unshift( { role:'user'+storeUser.sex, content:'', taskId:taskId, files:files },)

						}
						inPush.value = false
						nextTick(()=>{refTerminal.value.scrollToBottom(true)})
					},
					()=>{
						loading.value=false;
						replacePath('/chat/ask/')
					}
				)
			}
		},50)
	}
}

</script>

<template>
	<div class="container">
		<terminal :hidden="(!conversationId || conversationId==='') && hisList.length===0" :clear-history="!loading" ref="refTerminal" @onClearHistory="handleClearHistory">
			<a-spin v-if="loading" tip="加载对话..." style="padding-top:20vh"/>
			<template v-for="(item,index) in hisList" :key="index">
				<template v-if="item.role==='user1'||item.role==='user0'">
					<Bubble v-if="!item.files || item.files.length===0" placement="end" :content="item.content" :avatar="getAvatar(item.role)" shape="corner"/>
					<Bubble v-else placement="end" variant="borderless" :avatar="getAvatar(item.role)">
						<template #message>
							<a-flex wrap="wrap" gap="small"><Attachments.FileCard v-for="(file, index) in item.files" :key="index" :item="file" @click="handleAttachClick(file)"/></a-flex>
						</template>
					</Bubble>
				</template>
				<Bubble v-else-if="item.role==='assistant'" placement="start" :content="item.content"  variant="outlined" :avatar="getAvatar(item.role)" shape="corner"
						:loading="item.loading">
					<template #message>
						<think-process v-if="item.thinkStatus" :status="item.thinkStatus"><div class="md" v-html="md.render(item.thinkContent)"/></think-process>
						<div class="md" v-html="md.render(item.content)"/>
						<a-flex v-if="item.files && item.files.length>0" wrap="wrap" gap="small" style="padding-top:12px;"><Attachments.FileCard v-for="(file, index) in item.files" :key="index" :item="file" @click="handleAttachClick(file)"/></a-flex>
					</template>
					<template #footer>
						<a-space v-if="!inPush" size="small">
							<a-button type="text" size="small" :icon="h(SyncOutlined)" @click="reAsk(index)" />
							<a-button v-if="item.content && item.content.length>0" type="text" size="small" :icon="h(CopyOutlined)" @click="copyContent(item.content)" />
							<a-button v-if="audioSts===-1 || audioSts===1 || (audioSts===2&&speechText!==item.content)" title="语音播放" type="text" size="small" :icon="h(SoundOutlined)" @click="playSpeak(item.content)" />
							<LoadingOutlined v-else-if="audioSts===0 && speechText===item.content" style="color:var(--info-color)"/>
							<a-button v-else-if="audioSts===2 && speechText===item.content" title="暂停语音" style="color:var(--primary-color)" type="text" size="small" :icon="h(SoundFilled)" @click="pauseSpeak()" />
						</a-space>
					</template>
				</Bubble>
			</template>
		</terminal>
		<welcome-screen v-if="(!conversationId || conversationId==='') && hisList.length===0" @textSelected="askWithText"/>
		<sender-bar @onSend="handleSubmit" @onCancel="handleStop" ref="refSender" source="chat"/>
	</div>
</template>

<style scoped lang="scss">
:deep(.ant-avatar >img){width:32px;height:32px;}
//:deep(.ant-bubble-start .ant-bubble-content){ margin-right:44px; }
@media (max-width: 767px){
	:deep(.ant-bubble-avatar){display:none;}
}
:deep(.ant-bubble-content p:last-child){margin-bottom:0}
:deep(.ant-bubble .ant-bubble-footer) {
	margin-top:2px;
	.ant-btn{color:var(--font-secondary-color)}
	//.ant-btn.ant-btn-sm{font-size:16px;}
}
:deep(.ant-bubble-content .ant-attachment-list-card-status-done:not(.ant-attachment-list-card-status-error)){cursor:pointer;}
</style>